//
// Created by guo on 24-11-11.
//

#ifndef GENGINE_SHADER_H
#define GENGINE_SHADER_H
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include "glad/glad.h"

class Shader{
public:
    enum class ShaderType : uint32_t {
        vertex = GL_VERTEX_SHADER,
        frag = GL_FRAGMENT_SHADER,
        geo = GL_GEOMETRY_SHADER,
        tess_control = GL_TESS_CONTROL_SHADER,
        tess_eval = GL_TESS_EVALUATION_SHADER
    };

    enum class ErrorStep : uint8_t {
        compile,
        link
    };

    Shader(){
        _program = glCreateProgram();
    }
    [[nodiscard]]
    bool load(const std::string& vertex_path, const std::string& fragment_path,
              const std::string& geometry_path = "", const std::string& tess_control_path = "",
              const std::string& tess_eval_path = ""){
        GLuint vertex_shader;
        GLuint frag_shader;
        GLuint geo_shader;
        GLuint tess_ctrl_shader;
        GLuint tess_eval_shader;

        vertex_shader = glCreateShader(GL_VERTEX_SHADER);
        if(!attach(vertex_shader, vertex_path)){
            return false;
        }

        frag_shader = glCreateShader(GL_FRAGMENT_SHADER);
        if(!attach(frag_shader, fragment_path)){
            return false;
        }

        if(!geometry_path.empty()){
            geo_shader = glCreateShader(GL_GEOMETRY_SHADER);
            if(!attach(geo_shader, geometry_path)){
                return false;
            }
        }

        if(!tess_control_path.empty()){
            tess_ctrl_shader = glCreateShader(GL_TESS_CONTROL_SHADER);
            if(!attach(tess_ctrl_shader, tess_control_path)){
                return false;
            }
        }

        if(!tess_eval_path.empty()){
            tess_eval_shader = glCreateShader(GL_TESS_EVALUATION_SHADER);
            if(!attach(tess_eval_shader, tess_eval_path)){
                return false;
            }
        }

        if(!link()){
            return false;
        }

        glDeleteShader(vertex_shader);
        glDeleteShader(frag_shader);
        if(!geometry_path.empty()) {
            glDeleteShader(geo_shader);
        }
        if(!tess_control_path.empty()) {
            glDeleteShader(tess_ctrl_shader);
        }
        if(!tess_eval_path.empty()) {
            glDeleteShader(tess_eval_shader);
        }

        return true;
    }

    [[maybe_unused]]
    void use() const{
        glUseProgram(_program);
    }

    [[nodiscard]]
    unsigned int program() const{
        return _program;
    }

private:
    unsigned int _program;

    [[nodiscard]]
    bool link() const{
        glLinkProgram(_program);
        if(!check(_program, ErrorStep::link)){
            return false;
        }
        return true;
    }

    [[nodiscard]]
    bool attach(GLuint shader, const std::string& path) {
        std::ifstream ifs(path);
        if(!ifs.is_open()){
            std::cout << "open " << path << " failed !" << std::endl;
            return false;
        }

        std::stringstream ss;
        ss << ifs.rdbuf();
        std::string code = ss.str();
        const char* code_c = code.c_str();
        glShaderSource(shader, 1, &code_c, nullptr);
        glCompileShader(shader);
        if(!check(shader,ErrorStep::compile)){
            return false;
        }
        glAttachShader(_program, shader);

        return true;
    }
    [[nodiscard]]
    bool check(GLuint shader, ErrorStep step) const {
        int success;
        char error_info[512];
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            std::string error_msg;
            switch (step) {
                case ErrorStep::compile:{
                    error_msg+="compile step: ";
                    break;
                }

                case ErrorStep::link:{
                    error_msg+="link step: ";
                    break;
                }
            }
            glGetShaderInfoLog(shader, 512, NULL, error_info);
            error_msg+=error_info;
            std::cout << error_msg << std::endl;
            return false;
        }
        return true;
    }
};


#endif //GENGINE_SHADER_H
